/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package cz.cuni.mff.abacs.burglar.logics.planning;

import cz.cuni.amis.planning4j.*;
import cz.cuni.amis.planning4j.external.ExternalPlanner;
import cz.cuni.amis.planning4j.external.impl.itsimple.ItSimplePlannerExecutor;
import cz.cuni.amis.planning4j.external.impl.itsimple.ItSimplePlannerInformation;
import cz.cuni.amis.planning4j.external.impl.itsimple.PlannerListManager;
import cz.cuni.amis.planning4j.external.plannerspack.PlannersPackUtils;
import cz.cuni.amis.planning4j.impl.StringDomainProvider;
import cz.cuni.amis.planning4j.impl.StringProblemProvider;
import cz.cuni.amis.planning4j.pddl.PDDLRequirement;
import cz.cuni.mff.abacs.burglar.logics.GameMap;
import cz.cuni.mff.abacs.burglar.logics.objects.agents.Guard;
import cz.cuni.mff.abacs.burglar.logics.planning.instructions.Instruction;
import cz.cuni.mff.abacs.burglar.visual.VisualBurglar;
import java.io.*;
import java.util.EnumSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;


/**
 * Planninf4J planner library integration handler.
 * 
 * @author abacs
 */
public class Planning4JHandler extends PlannerHandlerBase implements PlannerHandler {
	
	/** Selected planner element.  */
	private ItSimplePlannerInformation _plannerElement = null;
	
	
	// -------------------------------------------------------------------------
	
	
	@Override
	public List<Instruction> solveBurglarProblem(
			GameMap map, 
			List<Integer> roomsToEnter, 
			List<Integer> roomsToAvoid
	) {
		
		String problemString = PDDLGenerator.generateBurglarProblem(map, roomsToEnter, roomsToAvoid);
		
		return solveProblem(map, problemString);
	}
	
	
	@Override
	public List<Instruction> solveGuardProblem(Guard guard, GameMap map, List<Integer> roomsToEnter, List<Integer> roomsToAvoid) {
		
		String problemString = PDDLGenerator.generateGuardProblem(map, guard, roomsToEnter, roomsToAvoid);
		
		return solveProblem(map, problemString);
	}
	
	
	// -------------------------------------------------------------------------
	
	
	/**
	 * Solves the problem handed over as a string parameter.
	 * 
	 * @param map world in witch the results can be interpreted.
	 * @param problemString problem string
	 * @return list of resulted instructions.
	 */
	private List<Instruction> solveProblem(
			GameMap map, 
			String problemString
	) {
		
		getPlanner();
		
		List<Instruction> instructions = new LinkedList<Instruction>();
		
		String domainString = readInFile(VisualBurglar.PATH_PLANNING + SUBPATH_DOMAINS + "agent.pddl");
		IDomainProvider domainProvider = new StringDomainProvider(domainString);
		
		IProblemProvider problemProvider = new StringProblemProvider(problemString);
		
		//This will be true with default maven project. If you have extracted the planners elsewhere, change it
		File plannersDirectory = new File("target");	// TODO ?????
		
		try{
			IPlanner planner = 
					new ExternalPlanner(
						new ItSimplePlannerExecutor(this._plannerElement, plannersDirectory)
					);
			
			IPlanningResult result = planner.plan(domainProvider, problemProvider);
			if(result.isSuccess() == false){
				System.out.println("Planning4JHandler: No solution found.");
				return instructions;
			}else{
				translateInstructions(result.getPlan(), map, instructions);
				for(ActionDescription action : result.getPlan()){
					System.out.println(action.getName());
				}
			}
			
		}catch(PlanningException ex){
			System.out.println("Planning4JHandler: Exception during planning.");
			ex.printStackTrace();
		}catch(Exception ex){
			ex.printStackTrace();
		}
		
		return instructions;
	}
	
	
	/** Selects a planner to be used based on the requirements. */
	private boolean getPlanner() {
		PlannerListManager plannerManager = PlannersPackUtils.getPlannerListManager();
		
		// Let the engine suggest as a planner that supports strips and runs on current platform
		
		Set<PDDLRequirement> requirements = EnumSet.of(PDDLRequirement.STRIPS, PDDLRequirement.TYPING);
		List<ItSimplePlannerInformation> suggestedPlanners = plannerManager.suggestPlanners(requirements);
		
		if(suggestedPlanners.isEmpty()){
			System.out.println("Planning4JHandler: No planner found for current platform.");
			return false;
		}
		
		//let's use the first suggested planner
		this._plannerElement = suggestedPlanners.get(0);
		
		return true;
	}
	
	
	/**
	 * Reads in a file.
	 * 
	 * Intended to be used to read in the PDDL domain file.
	 *  
	 * @param filename
	 * @return 
	 */
	private static String readInFile(String filename) {
		StringBuilder builder = new StringBuilder();
		try{
			// Open the file that is the first
			// command line parameter
			FileInputStream fstream = new FileInputStream(filename);
			// Get the object of DataInputStream
			DataInputStream in = new DataInputStream(fstream);
			BufferedReader br = new BufferedReader(new InputStreamReader(in));
			String strLine;
			// Read File Line By Line
			while ((strLine = br.readLine()) != null){
				// Print the content on the console
				builder.append(strLine);
				builder.append("\n");
			}
			// Close the input stream
			in.close();
		}catch (Exception e){	// Catch exception if any
			System.err.println("Planning4JHandler: Error: " + e.getMessage());
		}
		return builder.toString();
	}
	
	
	/**
	 * Goes though the input and translates 
	 * the instructions from planner specific format.
	 * 
	 * @param actions 
	 * @param map 
	 * @param instructions
	 * @throws IOException 
	 */
	private static void translateInstructions(
			List<ActionDescription> actions,
			GameMap map,
			List<Instruction> instructions
	) throws IOException {
		for(ActionDescription action : actions){
			translateInstruction(action, map, instructions);
		}
	}
	
	
	/**
	 * Translates a single instruction.
	 * 
	 * @param action action to translate
	 * @param map 
	 * @param instructions
	 * @return true if agent just entered a new room.
	 */
	private static boolean translateInstruction(
			ActionDescription action,
			GameMap map,
			List<Instruction> instructions
	) {
				
		// translate the instruction code:
		Instruction.code code = getInstructionCode(action.getName());
		
		List<String> params = action.getParameters();
		
		// get the agent:
		int agentId = -1;
		try{
			agentId = Integer.parseInt(params.get(0));
		}catch(NumberFormatException e){ 
			System.err.println(e.toString());
			e.printStackTrace();
		}
		
		int positionId;
		
		switch(code){
		case COMPLEX_MOVE:
			int moveToId = Integer.parseInt(params.get(2));
			
			instructions.add(new Instruction(code, agentId, moveToId));
			return false;
		case ENTER_DOOR:
			int doorId = Integer.parseInt(params.get(1));
			
			instructions.add(new Instruction(code, agentId, doorId));
			return true;
		case OPEN:
		case CLOSE:
		case USE:
			positionId = Integer.parseInt(params.get(1));
			
			instructions.add(new Instruction(code, agentId, positionId));
			return false;
		case UNLOCK:
		case LOCK:
		case PICK_UP:
			positionId = Integer.parseInt(params.get(1));
			int itemId = Integer.parseInt(params.get(2));
			
			instructions.add(
					new Instruction(code, agentId, positionId, itemId)
			);
			return false;
		default:
			
		}
		return false;
	}
	
	
}
